---
title: Next.js
icon: NextJs
---
import { Step, Steps } from "fumadocs-ui/components/steps";

This guide covers integrating `@daveyplate/better-auth-ui` into your Next.js project.

## Starter Project

Want to skip the installation? Check out the starter here:

- App Router: [GitHub](https://github.com/daveyplate/better-auth-nextjs-starter) - [Demo](https://nextjs.better-auth-starter.com)

- Pages Router:  [GitHub](https://github.com/daveyplate/better-auth-nextjs-pages-starter) - [Demo](https://nextjs-pages.better-auth-starter.com)

## App Router

Follow these steps to set up `@daveyplate/better-auth-ui` in your [Next.js](https://nextjs.org) project using the **App Router**:

<Steps>
<Step>
### AuthUIProvider

The first step is to set up the [`<AuthUIProvider />`](/components/auth-ui-provider) client component with your [`authClient`](https://www.better-auth.com/docs/installation#create-client-instance), wrapping your layout. This is required to provide the context & hooks to your authentication components across your application.

```tsx title="app/providers.tsx"
"use client"

import { AuthUIProvider } from "@daveyplate/better-auth-ui"
import Link from "next/link"
import { useRouter } from "next/navigation"
import type { ReactNode } from "react"

import { authClient } from "@/lib/auth-client"

export function Providers({ children }: { children: ReactNode }) {
    const router = useRouter()

    return (
        <AuthUIProvider
            authClient={authClient}
            navigate={router.push}
            replace={router.replace}
            onSessionChange={() => {
                // Clear router cache (protected routes)
                router.refresh()
            }}
            Link={Link}
        >
            {children}
        </AuthUIProvider>
    )
}
```

<small>
**Note**: Since the Next.js **App Router** caches routes by default, navigation to protected routes may fail until you perform a `router.refresh()` to clear the cache. To prevent this issue, you must use `router.refresh()` in the provided `onSessionChange` callback. This forces Next.js to clear the router cache and reload middleware-protected content, ensuring subsequent navigations accurately reflect the current auth state.
</small>

Once configured, wrap your layout component with the `Providers` component:

```tsx title="app/layout.tsx"
import type { ReactNode } from "react"
import { Providers } from "./providers"

export default function RootLayout({ children }: { children: ReactNode }) {
    return (
        <html lang="en">
            <body>
                <Providers>{children}</Providers>
            </body>
        </html>
    )
}
```

The [`<AuthUIProvider />`](/components/auth-ui-provider) can be fully customized with plugins, styles, localization and more. For more information and all available props, see the [`<AuthUIProvider />`](/components/auth-ui-provider) component documentation.
</Step>

<Step>
### Auth Pages

Create a dynamic route segment for authentication views in `app/auth/[path]/page.tsx`.

```tsx title="app/auth/[path]/page.tsx"
import { AuthView } from "@daveyplate/better-auth-ui"
import { authViewPaths } from "@daveyplate/better-auth-ui/server"

export const dynamicParams = false

export function generateStaticParams() {
    return Object.values(authViewPaths).map((path) => ({ path }))
}

export default async function AuthPage({ params }: { params: Promise<{ path: string }> }) {
    const { path } = await params
    
    return (
        <main className="container flex grow flex-col items-center justify-center self-center p-4 md:p-6">
            <AuthView path={path} />
        </main>
    )
}
```



The newly created dynamic route covers the following paths by default:

- `/auth/sign-in` – Sign in via email/password and social providers
- `/auth/sign-up` – New account registration
- `/auth/magic-link` – Email login without a password
- `/auth/forgot-password` – Trigger email to reset forgotten password
- `/auth/two-factor` – Two-factor authentication
- `/auth/recover-account` – Recover account via backup code
- `/auth/reset-password` – Set new password after receiving reset link
- `/auth/sign-out` – Log the user out of the application
- `/auth/callback` – Internal route to handle Auth callbacks
- `/auth/accept-invitation` – Accept an invitation to an organization

Ensure that any links to the authentication process utilize these routes accordingly. All routes will render the `<AuthView />` component and automatically handle navigation and authentication flow.
</Step>
<Step>
### Account Pages

```tsx title="app/account/[path]/page.tsx"
import { AccountView } from "@daveyplate/better-auth-ui"
import { accountViewPaths } from "@daveyplate/better-auth-ui/server"

export const dynamicParams = false

export function generateStaticParams() {
    return Object.values(accountViewPaths).map((path) => ({ path }))
}

export default async function AccountPage({ params }: { params: Promise<{ path: string }> }) {
    const { path } = await params

    return (
        <main className="container p-4 md:p-6">
            <AccountView path={path} />
        </main>
    )
}
```
</Step>
<Step>
### Organization Pages

```tsx title="app/organization/[path]/page.tsx"
import { OrganizationView } from "@daveyplate/better-auth-ui"
import { organizationViewPaths } from "@daveyplate/better-auth-ui/server"

export const dynamicParams = false

export function generateStaticParams() {
    return Object.values(organizationViewPaths).map((path) => ({ path }))
}

export default async function OrganizationPage({ params }: { params: Promise<{ path: string }> }) {
    const { path } = await params

    return (
        <main className="container p-4 md:p-6">
            <OrganizationView path={path} />
        </main>
    )
}
```

If you prefer slug-based org URLs, set `organization={{ pathMode: "slug", basePath: "/organization", slug: currentSlug }}` in the `AuthUIProvider` and structure your routes accordingly:

```tsx title="app/organization/[slug]/[path]/page.tsx"
import { OrganizationView } from "@daveyplate/better-auth-ui"
import { organizationViewPaths } from "@daveyplate/better-auth-ui/server"

export default async function OrganizationPage({ params }: { params: Promise<{ path: string }> }) {
    const { path } = await params

    return (
        <main className="container p-4 md:p-6">
            <OrganizationView path={path} />
        </main>
    )
}
```
</Step>
</Steps>

## Pages Router

Follow these steps to set up `@daveyplate/better-auth-ui` in your [Next.js](https://nextjs.org) project using the **Pages Router**:

<Steps>
<Step>
### AuthUIProvider

First set up the [`<AuthUIProvider />`](/components/auth-ui-provider) within your custom App component in `_app.tsx`.

```tsx title="pages/_app.tsx"
import type { AppProps } from "next/app"
import { AuthUIProvider } from "@daveyplate/better-auth-ui"
import { useRouter } from "next/router"
import Link from "next/link"

import { authClient } from "@/lib/auth-client"

export default function App({ Component, pageProps }: AppProps) {
    const router = useRouter()

    return (
        <AuthUIProvider
            authClient={authClient}
            navigate={router.push}
            replace={router.replace}
            Link={Link}
        >
            <Component {...pageProps} />
        </AuthUIProvider>
    )
}
```

Now the authentication context is available across your entire application.
</Step>

<Step>
### Auth Pages

Create a page with a dynamic segment in your Pages directory in `pages/auth/[authView].tsx`

```tsx title="pages/auth/[path].tsx"
import { AuthView } from "@daveyplate/better-auth-ui"
import { authViewPaths } from "@daveyplate/better-auth-ui/server"

export default function AuthPage({ path }: { path: string }) {
    return (
        <main className="container mx-auto flex grow flex-col items-center justify-center gap-3 self-center p-4 md:p-6">
            <AuthView path={path} />
        </main>
    )
}

export async function getStaticPaths() {
    return {
        paths: Object.values(authViewPaths).map((path) => ({ params: { path } })),
        fallback: false
    }
}

export async function getStaticProps({ params }: { params: { path: string } }) {
    return { props: { path: params.path } }
}
```

These routes match the list shown in the App Router section above.
</Step>
<Step>
### Account Pages

```tsx title="pages/account/[path].tsx"
import { AccountView } from "@daveyplate/better-auth-ui"
import { accountViewPaths } from "@daveyplate/better-auth-ui/server"

export default function AccountPage({ path }: { path: string }) {
    return (
        <main className="container mx-auto p-4 md:p-6">
            <AccountView path={path} />
        </main>
    )
}

export async function getStaticPaths() {
    return {
        paths: Object.values(accountViewPaths).map((path) => ({ params: { path } })),
        fallback: false
    }
}

export async function getStaticProps({ params }: { params: { path: string } }) {
    return { props: { path: params.path } }
}
```
</Step>
<Step>
### Organization Pages

```tsx title="pages/organization/[path].tsx"
import { OrganizationView } from "@daveyplate/better-auth-ui"
import { organizationViewPaths } from "@daveyplate/better-auth-ui/server"

export default function OrganizationPage({ path }: { path: string }) {
    return (
        <main className="container mx-auto p-4 md:p-6">
            <OrganizationView path={path} />
        </main>
    )
}

export async function getStaticPaths() {
    return {
        paths: Object.values(organizationViewPaths).map((path) => ({ params: { path } })),
        fallback: false
    }
}

export async function getStaticProps({ params }: { params: { path: string } }) {
    return { props: { organizationView: params.path } }
}
```
</Step>
</Steps>
